'use strict'

/**
 * <--    还原前    -->
let a = 1;
let b = eval('function incr_by_1(x){return x+a} debugger; var p = 9; let foo = 1; const PI = 3.14; incr_by_1(5); p = 100;');
 * 
 * <--    特点    -->
 * 见下 [对eval内置函数的理解]
 * 
 * <--    解决办法    -->
 * 见下 [对eval内置函数的理解]
 * 
 * <--    冗余判断    -->
 * 1. eval函数
 * 2. code为StringLiteral
 * 
 * <--    还原后    -->
let a = 1;

function incr_by_1(x) {
  return x + a;
}

var p = 9;
let b = (0, function () {
  debugger;
  let foo = 1;
  const PI = 3.14;
  incr_by_1(5);
  return p = 100;
})();
 * 
 **/

/**
 * 对eval内置函数的理解
 * 一. 为什么在调试eval函数时都会进入VM+xxx页面？
 *    eval本质上属于元编程范畴，会单独对字符串进行编译执行过程，
 *    但是之所以采用eval，往往是这个一段过程是动态构建的（如下示例），只有在执行过程中字符串生成好，才会将最终的字符串进行编译执行
 *    因为动态，所以需要另外构建页面debug最终的code
 * 二. eval函数执行时有什么特点？
 *    1. eval虽然是一个函数，但是有其特殊的实现机制，不能当普通函数调用时的调用栈执行上下文来分析
 *    2. 根据测试，编译code并执行，其内部通过<var, function>申明的变量会对影响eval所在作用域，而<let, const>声明的变量则不会影响。
 *    3. code中可以引用外部作用域的变量（如a）
 *    4. 从作用域上来说这两个特征非常类似语句块<BlockStatment>
 *    3. 最终的返回值为最后一个表达式的值，无需return语句
 * 三. 如何将eval(code)转换为节点？
 *    1. 由上特点可知，若利用语句块，则无法实现return；所以曲线救国，eval非常类似iife的特征，构建无参iife
 *    2. 但是考虑到var function声明对外部作用域的影响，需要分提升语句块、局部执行体语句块两部分
 *    2. 返回值呢？找到最后一个表达式，构建return语句
 */

// -------------------------------------  测试eval特性  -------------------------------------
function foo(){
    console.log('enter function foo:');
    let a = 1;
    let b = eval('function incr_by_'+a+'(x){return x+a} debugger; var p = 9; let foo = 1; const PI = 3.14; incr_by_'+a+'(5); p = 100;');
    console.log('b: ', b);    // b:  100
}

function bar(){
    console.log('enter function bar:');
    let a = 1;

    function incr_by_1(x) {
        return x + a;
    }

    var p = 9;
    let b = (0, function () {
        debugger;
        let foo = 1;
        const PI = 3.14;
        incr_by_1(5);
        return p = 100;
    })();
    console.log('b: ', b);    // b:  100
}

foo(), bar();
// -----------------------------------------------------------------------------------------

const {parse} = require('@babel/parser');
const traverse = require('@babel/traverse').default;
const t = require('@babel/types');
const generator = require('@babel/generator').default;

let code = `
let a = 1;
let b = eval('function incr_by_1(x){return x+a} debugger; var p = 9; let foo = 1; const PI = 3.14; incr_by_1(5); p = 100;');`

const visitor = {
    CallExpression:{
        enter:function(path){
            if(!t.isIdentifier(path.get('callee').node) || path.get('callee').node.name != 'eval'){return}
            if(path.get('arguments').length != 1 || !t.isStringLiteral(path.get('arguments.0').node)){return}
            let code = path.get('arguments.0').node.value;
            let ast = parse(code);
            
            // 拆分提升语句块，局部执行体语句块
            let hoistStmts = [], localStmts = [];
            for(let stmtNode of ast.program.body){
                if(t.isFunctionDeclaration(stmtNode) || t.isVariableDeclaration(stmtNode, {'kind': 'var'})){
                    hoistStmts.push(stmtNode);
                }else{
                    localStmts.push(stmtNode);
                }
            }

            // 提升语句块加入ast
            let stmtParent = path.getStatementParent();
            for(let hoistStmt of hoistStmts){
                stmtParent.insertBefore(hoistStmt);
            }

            // 局部执行体语句块构造iife，替换eval()
            let functionExpression = t.functionExpression(null,[],t.blockStatement(localStmts));
            // 构建函数表达式，用逗号表达式或者括号表达式都可以
            let sequenceExpression = t.sequenceExpression([t.numericLiteral(0), functionExpression]);
            // let parenthesizedExpression = t.parenthesizedExpression(functionExpression);
            path.replaceInline(t.callExpression(sequenceExpression,[]));
            
            // 构建return语句
            path.parentPath.traverse({
                CallExpression:{
                    enter:function(path){
                        if(!t.isSequenceExpression(path.get('callee').node) ||
                            path.get('arguments').length){
                                return
                            }
                        let lastStmt = path.get('callee.expressions.1.body.body.0');
                        let nextStmt = lastStmt.getNextSibling();
                        while(t.isNode(nextStmt.node)){
                            lastStmt = nextStmt;
                            nextStmt = nextStmt.getNextSibling();
                        }
                        if(t.isExpressionStatement(lastStmt.node)){
                            lastStmt.replaceInline(t.returnStatement(lastStmt.get('expression').node))
                        }else{
                            lastStmt.insertAfter(t.returnStatement())
                        }
                    },
                },
            });
        },
    },
}

let ast = parse(code);
traverse(ast, visitor);
console.log(generator(ast).code);
